#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <algorithm>
#include "Platform.h"
#include "Scintilla.h"
#include "SplitVector.h"
#include "Partitioning.h"
#include "CellBuffer.h"
#include "UniConversion.h"

#ifdef SCI_NAMESPACE
using namespace Scintilla;
#endif


LineChanges::LineChanges() : collecting( 0 ), edition( 0 ) {
}

LineChanges::~LineChanges() {
}

void LineChanges::AdvanceEdition() {
  edition = ( edition + 1 ) % 0x40000000;
}

int LineChanges::GetEdition() const {
  return edition;
}

char *LineChanges::PersistantForm() const {
  if( collecting ) {
    return state.PersistantForm();
  } else {
    return 0;
  }
}

void LineChanges::SetChanges( const char *changesState ) {
  if( collecting && changesState ) {
    state.FromPersistant( changesState );
    AdvanceEdition();
  }
}

void LineChanges::InsertText( int line, int edition, bool undoing ) {
  if( collecting && !undoing ) {
    int position = line;
    int fillLength = 1;
    if( state.FillRange( position, edition, fillLength ) ) {
      if( fillLength > 0 ) {
        AdvanceEdition();
      }
    }
  }
}

void LineChanges::InsertLine( int line, int edition, bool undoing ) {
  if( collecting && !undoing ) {
    state.InsertSpace( line, 1 );
    int linePosition = line;
    int fillLength = 1;
    if( state.FillRange( linePosition, edition, fillLength ) ) {
      AdvanceEdition();
    }
  }
}

void LineChanges::RemoveLine( int line, bool undoing ) {
  if( collecting && !undoing ) {
    state.DeleteRange( line, 1 );
    AdvanceEdition();
  }
}

void LineChanges::EnableChangeCollection( bool collecting_, int lines ) {
  collecting = collecting_;
  if( collecting ) {
    state.InsertSpace( 0, lines );
  }
}

void LineChanges::ClearChanged() {
  if( collecting ) {
    int position = 0;
    int length = state.Length();
    if( state.FillRange( position, 0, length ) ) {
      AdvanceEdition();
    }
  }
}

int LineChanges::GetChanged( int line ) const {
  if( collecting ) {
    return state.ValueAt( line );
  }
  return 0;
}


LineVector::LineVector() : starts( 256 ), perLine( 0 ) {
  Init();
}

LineVector::~LineVector() {
  starts.DeleteAll();
}

void LineVector::Init() {
  starts.DeleteAll();
  if( perLine ) {
    perLine->Init();
  }
}

void LineVector::SetPerLine( PerLine *pl ) {
  perLine = pl;
}


void LineVector::InsertText( int line, int delta, int edition, bool undoing, bool lineUnchanged ) {
  starts.InsertText( line, delta );
  if( !lineUnchanged ) {
    changes.InsertText( line, edition, undoing );
  }
}


void LineVector::InsertLine( int line, int position, bool lineStart, int edition, bool undoing ) {
  starts.InsertPartition( line, position );
  if( perLine ) {
    if( ( line > 0 ) && lineStart ) {
      line--;
    }
    perLine->InsertLine( line );
  }
  changes.InsertLine( line, edition, undoing );
}

void LineVector::SetLineStart( int line, int position ) {
  starts.SetPartitionStartPosition( line, position );
}


void LineVector::RemoveLine( int line, bool undoing ) {
  starts.RemovePartition( line );
  if( perLine ) {
    perLine->RemoveLine( line );
  }
  changes.RemoveLine( line, undoing );
}

int LineVector::LineFromPosition( int pos ) const {
  return starts.PartitionFromPosition( pos );
}

void LineVector::EnableChangeCollection( bool changesCollecting_ ) {
  DeleteChangeCollection();
  changes.EnableChangeCollection( changesCollecting_, Lines() );
}

void LineVector::DeleteChangeCollection() {
  changes.ClearChanged();
}

int LineVector::GetChanged( int line ) const {
  return changes.GetChanged( line );
}

int LineVector::GetChangesEdition() const {
  return changes.GetEdition();
}

void LineVector::SetSavePoint() {
  changes.AdvanceEdition();
}

char *LineVector::PersistantForm() const {
  return changes.PersistantForm();
}

void LineVector::SetChanges( const char *changesState ) {
  changes.SetChanges( changesState );
}


Action::Action() {
  at = startAction;
  position = 0;
  data = 0;
  lenData = 0;
  mayCoalesce = false;
}

Action::~Action() {
  Destroy();
}

void Action::Create( actionType at_, int position_, const char *data_, int lenData_, bool mayCoalesce_ ) {
  delete []data;
  data = NULL;
  position = position_;
  at = at_;
  if( lenData_ ) {
    data = new char[lenData_];
    memcpy( data, data_, lenData_ );
  }
  lenData = lenData_;
  mayCoalesce = mayCoalesce_;
}

void Action::Destroy() {
  delete []data;
  data = 0;
}

void Action::Grab( Action *source ) {
  delete []data;
  position = source->position;
  at = source->at;
  data = source->data;
  lenData = source->lenData;
  mayCoalesce = source->mayCoalesce;
  source->position = 0;
  source->at = startAction;
  source->data = 0;
  source->lenData = 0;
  source->mayCoalesce = true;
}
UndoHistory::UndoHistory() {
  lenActions = 100;
  actions = new Action[lenActions];
  maxAction = 0;
  currentAction = 0;
  undoSequenceDepth = 0;
  savePoint = 0;
  tentativePoint = -1;
  savePointEffective = 0;
  changeActions = 0;
  actions[currentAction].Create( startAction );
}

UndoHistory::~UndoHistory() {
  DeleteChangeHistory();
  delete []actions;
  actions = 0;
}

void UndoHistory::EnsureUndoRoom() {
  if( currentAction >= ( lenActions - 2 ) ) {
    int lenActionsNew = lenActions * 2;
    if( changeActions ) {
      int **changeActionsNew = new int *[lenActionsNew];
      if( !changeActionsNew ) {
        return;
      }
      for( int i = 0; i < lenActionsNew; i++ ) {
        changeActionsNew[i] = ( i < lenActions ) ? changeActions[i] : 0;
      }
      delete []changeActions;
      changeActions = changeActionsNew;
    }
    Action *actionsNew = new Action[lenActionsNew];
    for( int act = 0; act <= currentAction; act++ ) {
      actionsNew[act].Grab( &actions[act] );
    }
    delete []actions;
    lenActions = lenActionsNew;
    actions = actionsNew;
  }
}


const char * UndoHistory::AppendAction( actionType at, int position, const char *data, int lengthData,
                                        bool &startSequence, char *persistantChanges, bool mayCoalesce ) {
  EnsureUndoRoom();
  if( currentAction < savePoint ) {
    savePointEffective = currentAction;
    savePoint = -1;
  }
  int oldCurrentAction = currentAction;
  if( currentAction >= 1 ) {
    if( 0 == undoSequenceDepth ) {
      int targetAct = -1;
      const Action *actPrevious = &( actions[currentAction + targetAct] );
      while( ( actPrevious->at == containerAction ) && actPrevious->mayCoalesce ) {
        targetAct--;
        actPrevious = &( actions[currentAction + targetAct] );
      }
      #if defined(_MSC_VER) && defined(_PREFAST_)
      __analysis_assume( actions );
      #endif
      if( ( currentAction == savePoint ) || ( currentAction == tentativePoint ) ) {
        currentAction++;
      } else if( !actions[currentAction].mayCoalesce ) {
        currentAction++;
      } else if( !mayCoalesce || !actPrevious->mayCoalesce ) {
        currentAction++;
      } else if( at == containerAction || actions[currentAction].at == containerAction )
        ;
      else if( ( at != actPrevious->at ) && ( actPrevious->at != startAction ) ) {
        currentAction++;
      } else if( ( at == insertAction ) &&
                 ( position != ( actPrevious->position + actPrevious->lenData ) ) ) {
        currentAction++;
      } else if( at == removeAction ) {
        if( ( lengthData == 1 ) || ( lengthData == 2 ) ) {
          if( ( position + lengthData ) == actPrevious->position )
            ;
          else if( position == actPrevious->position )
            ;
          else
          { currentAction++; }
        } else
        { currentAction++; }
      } else {
      }
    } else {
      if( !actions[currentAction].mayCoalesce ) {
        currentAction++;
      }
    }
  } else {
    currentAction++;
  }
  startSequence = oldCurrentAction != currentAction;
  int actionWithData = currentAction;
  actions[currentAction].Create( at, position, data, lengthData, mayCoalesce );
  if( changeActions ) {
    delete []changeActions[currentAction];
    changeActions[currentAction] = ( int * )persistantChanges;
  }
  currentAction++;
  actions[currentAction].Create( startAction );
  maxAction = currentAction;
  return actions[actionWithData].data;
}

void UndoHistory::BeginUndoAction() {
  EnsureUndoRoom();
  if( undoSequenceDepth == 0 ) {
    if( actions[currentAction].at != startAction ) {
      currentAction++;
      actions[currentAction].Create( startAction );
      maxAction = currentAction;
    }
    actions[currentAction].mayCoalesce = false;
  }
  undoSequenceDepth++;
}

void UndoHistory::EndUndoAction() {
  PLATFORM_ASSERT( undoSequenceDepth > 0 );
  EnsureUndoRoom();
  undoSequenceDepth--;
  if( 0 == undoSequenceDepth ) {
    if( actions[currentAction].at != startAction ) {
      currentAction++;
      actions[currentAction].Create( startAction );
      maxAction = currentAction;
    }
    actions[currentAction].mayCoalesce = false;
  }
}

void UndoHistory::DropUndoSequence() {
  undoSequenceDepth = 0;
}

void UndoHistory::DeleteUndoHistory() {
  for( int i = 1; i < maxAction; i++ ) {
    actions[i].Destroy();
  }
  maxAction = 0;
  currentAction = 0;
  actions[currentAction].Create( startAction );
  savePoint = 0;
  tentativePoint = -1;
  savePointEffective = 0;
}


void UndoHistory::DeleteChangeHistory() {
  if( changeActions ) {
    for( int i = 0; i < lenActions; i++ ) {
      delete []changeActions[i];
    }
    delete []changeActions;
    changeActions = 0;
  }
}

void UndoHistory::EnableChangeHistory( bool enable ) {
  if( enable ) {
    if( !changeActions ) {
      changeActions = new int *[lenActions];
      for( int i = 0; i < lenActions; i++ ) {
        changeActions[i] = 0;
      }
    }
  } else {
    DeleteChangeHistory();
  }
}


void UndoHistory::SetSavePoint() {
  savePoint = currentAction;
  savePointEffective = currentAction;
}

bool UndoHistory::IsSavePoint() const {
  return savePoint == currentAction;
}


bool UndoHistory::BeforeSavePointEffective( int action ) const {
  return action <= savePointEffective;
}


void UndoHistory::TentativeStart() {
  tentativePoint = currentAction;
}

void UndoHistory::TentativeCommit() {
  tentativePoint = -1;
  maxAction = currentAction;
}

int UndoHistory::TentativeSteps() {
  if( actions[currentAction].at == startAction && currentAction > 0 ) {
    currentAction--;
  }
  if( tentativePoint >= 0 ) {
    return currentAction - tentativePoint;
  } else {
    return -1;
  }
}

bool UndoHistory::CanUndo() const {
  return ( currentAction > 0 ) && ( maxAction > 0 );
}

int UndoHistory::StartUndo() {
  if( actions[currentAction].at == startAction && currentAction > 0 ) {
    currentAction--;
  }
  int act = currentAction;
  while( actions[act].at != startAction && act > 0 ) {
    act--;
  }
  return currentAction - act;
}

const Action &UndoHistory::GetUndoStep() const {
  return actions[currentAction];
}

void UndoHistory::CompletedUndoStep() {
  currentAction--;
}


char *UndoHistory::GetChangesStep() const {
  return changeActions ? ( char * )changeActions[currentAction] : 0;
}


bool UndoHistory::CanRedo() const {
  return maxAction > currentAction;
}

int UndoHistory::StartRedo() {
  if( actions[currentAction].at == startAction && currentAction < maxAction ) {
    currentAction++;
  }
  int act = currentAction;
  while( actions[act].at != startAction && act < maxAction ) {
    act++;
  }
  return act - currentAction;
}

const Action &UndoHistory::GetRedoStep() const {
  return actions[currentAction];
}

void UndoHistory::CompletedRedoStep() {
  currentAction++;
}


int UndoHistory::Edition() const {
  return currentAction;
}


CellBuffer::CellBuffer() {
  readOnly = false;
  utf8LineEnds = 0;
  collectingUndo = true;
}

CellBuffer::~CellBuffer() {
}

char CellBuffer::CharAt( int position ) const {
  return substance.ValueAt( position );
}

void CellBuffer::GetCharRange( char *buffer, int position, int lengthRetrieve ) const {
  if( lengthRetrieve <= 0 ) {
    return;
  }
  if( position < 0 ) {
    return;
  }
  if( ( position + lengthRetrieve ) > substance.Length() ) {
    Platform::DebugPrintf( "Bad GetCharRange %d for %d of %d\n", position,
                           lengthRetrieve, substance.Length() );
    return;
  }
  substance.GetRange( buffer, position, lengthRetrieve );
}

char CellBuffer::StyleAt( int position ) const {
  return style.ValueAt( position );
}

void CellBuffer::GetStyleRange( unsigned char *buffer, int position, int lengthRetrieve ) const {
  if( lengthRetrieve < 0 ) {
    return;
  }
  if( position < 0 ) {
    return;
  }
  if( ( position + lengthRetrieve ) > style.Length() ) {
    Platform::DebugPrintf( "Bad GetStyleRange %d for %d of %d\n", position,
                           lengthRetrieve, style.Length() );
    return;
  }
  style.GetRange( reinterpret_cast<char *>( buffer ), position, lengthRetrieve );
}

const char *CellBuffer::BufferPointer() {
  return substance.BufferPointer();
}

const char *CellBuffer::RangePointer( int position, int rangeLength ) {
  return substance.RangePointer( position, rangeLength );
}

int CellBuffer::GapPosition() const {
  return substance.GapPosition();
}


const char *CellBuffer::InsertString( int position, const char *s, int insertLength, bool &startSequence ) {
  const char *data = s;
  if( !readOnly ) {
    if( collectingUndo ) {
      char *persistantForm = lv.PersistantForm();
      data = uh.AppendAction( insertAction, position, s, insertLength, startSequence, persistantForm );
    }
    BasicInsertString( position, s, insertLength, false );
  }
  return data;
}

bool CellBuffer::SetStyleAt( int position, char styleValue ) {
  char curVal = style.ValueAt( position );
  if( curVal != styleValue ) {
    style.SetValueAt( position, styleValue );
    return true;
  } else {
    return false;
  }
}

bool CellBuffer::SetStyleFor( int position, int lengthStyle, char styleValue ) {
  bool changed = false;
  PLATFORM_ASSERT( lengthStyle == 0 ||
                   ( lengthStyle > 0 && lengthStyle + position <= style.Length() ) );
  while( lengthStyle-- ) {
    char curVal = style.ValueAt( position );
    if( curVal != styleValue ) {
      style.SetValueAt( position, styleValue );
      changed = true;
    }
    position++;
  }
  return changed;
}


const char *CellBuffer::DeleteChars( int position, int deleteLength, bool &startSequence ) {
  PLATFORM_ASSERT( deleteLength > 0 );
  const char *data = 0;
  if( !readOnly ) {
    if( collectingUndo ) {
      data = substance.RangePointer( position, deleteLength );
      char *persistantForm = lv.PersistantForm();
      data = uh.AppendAction( removeAction, position, data, deleteLength, startSequence, persistantForm );
    }
    BasicDeleteChars( position, deleteLength, false );
  }
  return data;
}

int CellBuffer::Length() const {
  return substance.Length();
}

void CellBuffer::Allocate( int newSize ) {
  substance.ReAllocate( newSize );
  style.ReAllocate( newSize );
}

void CellBuffer::SetLineEndTypes( int utf8LineEnds_ ) {
  if( utf8LineEnds != utf8LineEnds_ ) {
    utf8LineEnds = utf8LineEnds_;
    ResetLineEnds( false );
  }
}

void CellBuffer::SetPerLine( PerLine *pl ) {
  lv.SetPerLine( pl );
}

int CellBuffer::Lines() const {
  return lv.Lines();
}

int CellBuffer::LineStart( int line ) const {
  if( line < 0 ) {
    return 0;
  } else if( line >= Lines() ) {
    return Length();
  } else {
    return lv.LineStart( line );
  }
}

bool CellBuffer::IsReadOnly() const {
  return readOnly;
}

void CellBuffer::SetReadOnly( bool set ) {
  readOnly = set;
}

void CellBuffer::SetSavePoint() {
  uh.SetSavePoint();
  lv.SetSavePoint();
}

bool CellBuffer::IsSavePoint() const {
  return uh.IsSavePoint();
}

void CellBuffer::TentativeStart() {
  uh.TentativeStart();
}

void CellBuffer::TentativeCommit() {
  uh.TentativeCommit();
}

int CellBuffer::TentativeSteps() {
  return uh.TentativeSteps();
}

bool CellBuffer::TentativeActive() const {
  return uh.TentativeActive();
}




void CellBuffer::InsertLine( int line, int position, bool lineStart, int edition, bool undoing ) {
  lv.InsertLine( line, position, lineStart, edition, undoing );
}


void CellBuffer::RemoveLine( int line, bool undoing ) {
  lv.RemoveLine( line, undoing );
}

bool CellBuffer::UTF8LineEndOverlaps( int position ) const {
  unsigned char bytes[] = {
    static_cast<unsigned char>( substance.ValueAt( position - 2 ) ),
    static_cast<unsigned char>( substance.ValueAt( position - 1 ) ),
    static_cast<unsigned char>( substance.ValueAt( position ) ),
    static_cast<unsigned char>( substance.ValueAt( position + 1 ) ),
  };
  return UTF8IsSeparator( bytes ) || UTF8IsSeparator( bytes + 1 ) || UTF8IsNEL( bytes + 1 );
}


void CellBuffer::ResetLineEnds( bool undoing ) {
  lv.Init();
  int position = 0;
  int length = Length();
  int lineInsert = 1;
  bool atLineStart = true;
  lv.InsertText( lineInsert - 1, length, uh.Edition(), undoing, false );
  unsigned char chBeforePrev = 0;
  unsigned char chPrev = 0;
  for( int i = 0; i < length; i++ ) {
    unsigned char ch = substance.ValueAt( position + i );
    if( ch == '\r' ) {
      InsertLine( lineInsert, ( position + i ) + 1, atLineStart, uh.Edition(), undoing );
      lineInsert++;
    } else if( ch == '\n' ) {
      if( chPrev == '\r' ) {
        lv.SetLineStart( lineInsert - 1, ( position + i ) + 1 );
      } else {
        InsertLine( lineInsert, ( position + i ) + 1, atLineStart, uh.Edition(), undoing );
        lineInsert++;
      }
    } else if( utf8LineEnds ) {
      unsigned char back3[3] = {chBeforePrev, chPrev, ch};
      if( UTF8IsSeparator( back3 ) || UTF8IsNEL( back3 + 1 ) ) {
        InsertLine( lineInsert, ( position + i ) + 1, atLineStart, uh.Edition(), undoing );
        lineInsert++;
      }
    }
    chBeforePrev = chPrev;
    chPrev = ch;
  }
}


void CellBuffer::BasicInsertString( int position, const char *s, int insertLength, bool undoing ) {
  bool atFileEnd = position == substance.Length();
  if( insertLength == 0 ) {
    return;
  }
  PLATFORM_ASSERT( insertLength > 0 );
  unsigned char chAfter = substance.ValueAt( position );
  bool breakingUTF8LineEnd = false;
  if( utf8LineEnds && UTF8IsTrailByte( chAfter ) ) {
    breakingUTF8LineEnd = UTF8LineEndOverlaps( position );
  }
  substance.InsertFromArray( position, s, 0, insertLength );
  style.InsertValue( position, insertLength, 0 );
  int lineInsert = lv.LineFromPosition( position ) + 1;
  bool atLineStart = lv.LineStart( lineInsert - 1 ) == position;
  bool atLineEnd = ( lv.LineStart( lineInsert ) == position + 1 ) || atFileEnd;
  bool lineUnchanged = ( atLineStart && ( s[insertLength - 1] == '\n' ) ) ||
                       ( atLineEnd && ( s[0] == '\r' || s[0] == '\n' ) );
  lv.InsertText( lineInsert - 1, insertLength, uh.Edition(), undoing, lineUnchanged );
  unsigned char chBeforePrev = substance.ValueAt( position - 2 );
  unsigned char chPrev = substance.ValueAt( position - 1 );
  if( chPrev == '\r' && chAfter == '\n' ) {
    InsertLine( lineInsert, position, false, uh.Edition(), undoing );
    lineInsert++;
  }
  if( breakingUTF8LineEnd ) {
    RemoveLine( lineInsert, undoing );
  }
  unsigned char ch = ' ';
  for( int i = 0; i < insertLength; i++ ) {
    ch = s[i];
    if( ch == '\r' ) {
      InsertLine( lineInsert, ( position + i ) + 1, atLineStart, uh.Edition(), undoing );
      lineInsert++;
    } else if( ch == '\n' ) {
      if( chPrev == '\r' ) {
        lv.SetLineStart( lineInsert - 1, ( position + i ) + 1 );
      } else {
        InsertLine( lineInsert, ( position + i ) + 1, atLineStart, uh.Edition(), undoing );
        lineInsert++;
      }
    } else if( utf8LineEnds ) {
      unsigned char back3[3] = {chBeforePrev, chPrev, ch};
      if( UTF8IsSeparator( back3 ) || UTF8IsNEL( back3 + 1 ) ) {
        InsertLine( lineInsert, ( position + i ) + 1, atLineStart, uh.Edition(), undoing );
        lineInsert++;
      }
    }
    chBeforePrev = chPrev;
    chPrev = ch;
  }
  if( chAfter == '\n' ) {
    if( ch == '\r' ) {
      RemoveLine( lineInsert - 1, undoing );
    }
  } else if( utf8LineEnds && !UTF8IsAscii( chAfter ) ) {
    for( int j = 0; j < UTF8SeparatorLength - 1; j++ ) {
      unsigned char chAt = substance.ValueAt( position + insertLength + j );
      unsigned char back3[3] = {chBeforePrev, chPrev, chAt};
      if( UTF8IsSeparator( back3 ) ) {
        InsertLine( lineInsert, ( position + insertLength + j ) + 1, atLineStart, uh.Edition(), undoing );
        lineInsert++;
      }
      if( ( j == 0 ) && UTF8IsNEL( back3 + 1 ) ) {
        InsertLine( lineInsert, ( position + insertLength + j ) + 1, atLineStart, uh.Edition(), undoing );
        lineInsert++;
      }
      chBeforePrev = chPrev;
      chPrev = chAt;
    }
  }
}


void CellBuffer::BasicDeleteChars( int position, int deleteLength, bool undoing ) {
  if( deleteLength == 0 ) {
    return;
  }
  if( ( position == 0 ) && ( deleteLength == substance.Length() ) ) {
    lv.Init();
    lv.InsertText( 0, 0, uh.Edition(), undoing, false );
  } else {
    int lineRemove = lv.LineFromPosition( position ) + 1;
    bool atLineEnd = ( lv.LineStart( lineRemove ) == position + 1 );
    char chAfter = substance.ValueAt( position + deleteLength );
    bool lineUnchanged = ( atLineEnd && ( chAfter == '\r' || chAfter == '\n' ) );
    lv.InsertText( lineRemove - 1, - ( deleteLength ), uh.Edition(), undoing, lineUnchanged );
    unsigned char chPrev = substance.ValueAt( position - 1 );
    unsigned char chBefore = chPrev;
    unsigned char chNext = substance.ValueAt( position );
    bool ignoreNL = false;
    if( chPrev == '\r' && chNext == '\n' ) {
      lv.SetLineStart( lineRemove, position );
      lineRemove++;
      ignoreNL = true;
    }
    if( utf8LineEnds && UTF8IsTrailByte( chNext ) ) {
      if( UTF8LineEndOverlaps( position ) ) {
        RemoveLine( lineRemove, undoing );
      }
    }
    unsigned char ch = chNext;
    for( int i = 0; i < deleteLength; i++ ) {
      chNext = substance.ValueAt( position + i + 1 );
      if( ch == '\r' ) {
        if( chNext != '\n' ) {
          RemoveLine( lineRemove, undoing );
        }
      } else if( ch == '\n' ) {
        if( ignoreNL ) {
          ignoreNL = false;
        } else
        { RemoveLine( lineRemove, undoing ); }
      } else if( utf8LineEnds ) {
        if( !UTF8IsAscii( ch ) ) {
          unsigned char next3[3] = {ch, chNext,
                                    static_cast<unsigned char>( substance.ValueAt( position + i + 2 ) )
                                   };
          if( UTF8IsSeparator( next3 ) || UTF8IsNEL( next3 ) ) {
            RemoveLine( lineRemove, undoing );
          }
        }
      }
      ch = chNext;
    }
    if( chBefore == '\r' && chAfter == '\n' ) {
      RemoveLine( lineRemove - 1, undoing );
      lv.SetLineStart( lineRemove - 1, position + 1 );
    }
  }
  substance.DeleteRange( position, deleteLength );
  style.DeleteRange( position, deleteLength );
}

bool CellBuffer::SetUndoCollection( bool collectUndo ) {
  collectingUndo = collectUndo;
  uh.DropUndoSequence();
  return collectingUndo;
}

bool CellBuffer::IsCollectingUndo() const {
  return collectingUndo;
}

void CellBuffer::BeginUndoAction() {
  uh.BeginUndoAction();
}

void CellBuffer::EndUndoAction() {
  uh.EndUndoAction();
}

void CellBuffer::AddUndoAction( int token, bool mayCoalesce ) {
  bool startSequence;
  char *persistantForm = lv.PersistantForm();
  uh.AppendAction( containerAction, token, 0, 0, startSequence, persistantForm, mayCoalesce );
}


void CellBuffer::DeleteUndoHistory( bool collectChangeHistory ) {
  uh.DeleteUndoHistory();
  uh.EnableChangeHistory( collectChangeHistory );
  lv.EnableChangeCollection( collectChangeHistory );
}


bool CellBuffer::SetChangeCollection( bool collectChange ) {
  uh.EnableChangeHistory( collectChange );
  lv.EnableChangeCollection( collectChange );
  return collectChange;
}

void CellBuffer::DeleteChangeCollection() {
  uh.DeleteChangeHistory();
  lv.DeleteChangeCollection();
}


bool CellBuffer::CanUndo() const {
  return uh.CanUndo();
}

int CellBuffer::StartUndo() {
  return uh.StartUndo();
}

const Action &CellBuffer::GetUndoStep() const {
  return uh.GetUndoStep();
}

void CellBuffer::PerformUndoStep() {
  const char *changesState = uh.GetChangesStep();
  lv.SetChanges( changesState );
  const Action &actionStep = uh.GetUndoStep();
  if( actionStep.at == insertAction ) {
    BasicDeleteChars( actionStep.position, actionStep.lenData, true );
  } else if( actionStep.at == removeAction ) {
    BasicInsertString( actionStep.position, actionStep.data, actionStep.lenData, true );
  }
  uh.CompletedUndoStep();
}

bool CellBuffer::CanRedo() const {
  return uh.CanRedo();
}

int CellBuffer::StartRedo() {
  return uh.StartRedo();
}

const Action &CellBuffer::GetRedoStep() const {
  return uh.GetRedoStep();
}

void CellBuffer::PerformRedoStep() {
  const Action &actionStep = uh.GetRedoStep();
  if( actionStep.at == insertAction ) {
    BasicInsertString( actionStep.position, actionStep.data, actionStep.lenData, false );
  } else if( actionStep.at == removeAction ) {
    BasicDeleteChars( actionStep.position, actionStep.lenData, false );
  }
  uh.CompletedRedoStep();
  if( IsSavePoint() ) {
    lv.SetSavePoint();
  }
}


int CellBuffer::GetChanged( int line ) const {
  int changed = lv.GetChanged( line );
  if( changed == 0 ) {
    return 0;
  } else if( uh.BeforeSavePointEffective( changed ) ) {
    return 2;
  } else {
    return 1;
  }
}

int CellBuffer::GetChangesEdition() const {
  return lv.GetChangesEdition();
}

